package net.crowtheries.wirelessrouting;

import java.util.HashMap;
import java.util.HashSet;


import ptolemy.actor.TypedAtomicActor;
import ptolemy.data.BooleanToken;
import ptolemy.data.IntToken;
import ptolemy.data.RecordToken;
import ptolemy.data.StringToken;
import ptolemy.data.Token;
import ptolemy.data.expr.StringParameter;
import ptolemy.domains.wireless.kernel.WirelessIOPort;
import ptolemy.kernel.CompositeEntity;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.NameDuplicationException;

public class RoutesManager extends TypedAtomicActor{
	public RoutesManager(CompositeEntity container,String name) throws IllegalActionException, NameDuplicationException{
		super(container,name);
		reqHop=new WirelessIOPort(this,"reqHop",true,false);
		nxtHop=new WirelessIOPort(this,"nxtHop",false,true);
		updateEntries=new WirelessIOPort(this,"updateEntries",true,false);
		reqRoute=new WirelessIOPort(this,"reqRoute",false,true);
        entryStatus=new WirelessIOPort(this,"entryStatus",false,true);
		
		_routingTable=new HashMap<String, Object[]>();
	}
	
	///////// Ports /////
	/**
	 * 
	 */
	WirelessIOPort reqHop;
	WirelessIOPort nxtHop;
	WirelessIOPort reqRoute;
	WirelessIOPort updateEntries;
    /**
     * Returns the 
     */
    WirelessIOPort entryStatus;
	
	///////// Private vars //////
	/**
	 * Contains the routes :
	 * key=DST_ADR
	 * Value= [ NXT_HOP , DST_SEQ , HOPCOUNT, LIFE ]
	 */
	HashMap<String, Object[]> _routingTable;
	
	/**
	 * Contains a cache of all the requests coming from FH
	 * it'll be to avoid sending "useless" messages to FH 
	 * for example when we make reverse routes
	 */
	HashSet<StringToken> _cacheIP=new HashSet<StringToken>();
	
	String _localIP;
	
	public void initialize(){
		StringParameter localip=(StringParameter)this.getContainer().getAttribute("localIP");
		_localIP=localip.getValueAsString();
	}
	
	public void fire() throws IllegalActionException{
		
		/*
		 * If we get a request for a route
		 */
		if(reqHop.hasToken(0)){			
			StringToken dstIP=(StringToken)reqHop.get(0);
			
			//we add the IP to the Cache
			_cacheIP.add(dstIP);
			
			//if the destination IP is in te table we send it directly to the "nxtHop" port to be used by the FlowHandler 
			if(_routingTable.get(dstIP.stringValue())!=null)
				nxtHop.send(0, new StringToken((String)_routingTable.get(dstIP.stringValue())[0]));
			
			//if we dont have it, we forward the ip to the protocol manager so he handles it by making the RREQ...
			else
				reqRoute.send(0, dstIP);
		}
		
		if(updateEntries.hasToken(0)){
            RecordToken operation=(RecordToken)updateEntries.get(0);
            int opCode=((IntToken)operation.get("OPC")).intValue();
            switch(opCode){
                //Code for adding an entry
                case 1:
                    break;

                //Code for Deleting an entry
                case 2:
                    break;

                //Code for Updating (it's both add/update) an entry
                case 3:
                    RecordToken message=(RecordToken)operation.get("MSG");
                    String IP=((StringToken)message.get("IP")).stringValue();
                    String NEXTHOP=((StringToken)message.get("NEXTHOP")).stringValue();
                    int DSTSEQ=((IntToken)message.get("DSTSEQ")).intValue();
                    int HOPCOUNT=((IntToken)message.get("HOPCOUNT")).intValue();
                    if(_routingTable.containsKey(IP)){
                        //UPDATE
                        this.updateEntry(IP, NEXTHOP, DSTSEQ, HOPCOUNT);
                    } else {
                        //ADD
                        this.addEntry(IP, NEXTHOP, DSTSEQ, HOPCOUNT);
                    }
                    break;

                //Code for requesting whether the entry exists or not
                case 4:
                    entryStatus.send(0,
                            new RecordToken(new String[]{
                                "IP",
                                "STATUS"
                            },new Token[]{
                                (StringToken)operation.get("IP"),
                                new BooleanToken(_routingTable.containsKey(
                                ((StringToken)operation.get("IP")).stringValue()))
                            }));
                            
                    break;
                default:
                    //do Something?
            }
		}
	}

    private void addEntry(String ORIGIP, String NEXTHOP, int DSTSEQ, int HOPCOUNT){
        this._routingTable.put(ORIGIP, new Object[]{
            NEXTHOP,
            DSTSEQ,
            HOPCOUNT,
            //TODO: manage LIFE properly
            255
        });
    }

    private void updateEntry(String ORIGIP, String NEXTHOP, int DSTSEQ, int HOPCOUNT){
        this._routingTable.remove(ORIGIP);
        this.addEntry(ORIGIP, NEXTHOP, DSTSEQ, HOPCOUNT);
    }
}
